#pragma warning(disable:4244) // double to float
#pragma warning(disable:4305) // double to float

#pragma comment( lib, "glew32.lib" )
//#pragma comment( lib, "glew32s.lib" )
#pragma comment( lib, "glfwdll.lib" )
#pragma comment( lib, "opengl32.lib" ) // not needed?

#include <GL\glew.h>
#include <GL\glfw.h>

#define _USE_MATH_DEFINES
#include <cmath>
#include <vector>

#include <algorithm>
#include <cmath>
#include <iostream>

#include <ctime>
#include <cassert>
//#include <graphicsmanager.h>

#include "TesselCube.h"

// we need to pass it as a string to the GLSL driver
char *textFileRead(char *fn) 
{
	FILE *fp;
	char *content = NULL;

	int count=0;

	if (fn != NULL) {

		fopen_s( &fp ,fn,"rt");

		if (fp != NULL) {

			fseek(fp, 0, SEEK_END);
			count = ftell(fp);
			rewind(fp);

			if (count > 0) {
				content = (char *)malloc(sizeof(char) * (count+1));
				count = fread(content,sizeof(char),count,fp);
				content[count] = '\0';
			}
			fclose(fp);

		}
	}

	return content;
}



//Got this from http://www.lighthouse3d.com/opengl/glsl/index.php?oglinfo
// it prints out shader info (debugging!)
void printShaderInfoLog(GLuint obj)
{
	int infologLength = 0;
	int charsWritten  = 0;
	char *infoLog;
	glGetShaderiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
	if (infologLength > 0)
	{
		infoLog = (char *)malloc(infologLength);
		glGetShaderInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("printShaderInfoLog: %s\n",infoLog);
		free(infoLog);
	}else{
		printf("Shader Info Log: OK\n");
	}
}

//Got this from http://www.lighthouse3d.com/opengl/glsl/index.php?oglinfo
// it prints out shader info (debugging!)
void printProgramInfoLog(GLuint obj)
{
	int infologLength = 0;
	int charsWritten  = 0;
	char *infoLog;
	glGetProgramiv(obj, GL_INFO_LOG_LENGTH,&infologLength);
	if (infologLength > 0)
	{
		infoLog = (char *)malloc(infologLength);
		glGetProgramInfoLog(obj, infologLength, &charsWritten, infoLog);
		printf("printProgramInfoLog: %s\n",infoLog);
		free(infoLog);
	}else{
		printf("Program Info Log: OK\n");
	}
}


class ShaderManager
{
private:
	GLuint v;
	GLuint f;
	GLuint p;

public:
	void setShaders() {

		char *vs,*fs;

		v = glCreateShader(GL_VERTEX_SHADER);
		f = glCreateShader(GL_FRAGMENT_SHADER);	

		vs = textFileRead("perpixel.vert");
		fs = textFileRead("perpixel.frag");

		const char * vv = vs;
		const char * ff = fs;

		glShaderSource(v, 1, &vv,NULL);
		glShaderSource(f, 1, &ff,NULL);

		free(vs);free(fs);

		GLint VSCompiled, FSCompiled, linked;

		glCompileShader(v);
		glGetShaderiv(v, GL_COMPILE_STATUS, &VSCompiled);
		if ( !VSCompiled )
		{
			std::cout << "error when compiling vertex shader: " << "perpixel.vert" << "\n\n";
			printShaderInfoLog(v);
		}

		glCompileShader(f);
		glGetShaderiv(f, GL_COMPILE_STATUS, &FSCompiled);
		if ( !VSCompiled )
		{
			std::cout << "error when compiling frag shader: " << "perpixel.frag" << "\n\n";
			printShaderInfoLog(f);
		}


		p = glCreateProgram();

		glAttachShader(p,v);
		glAttachShader(p,f);
		glBindFragDataLocationEXT(p, 0, "basicFrag"); 

		glLinkProgram(p);
		glGetProgramiv(p, GL_LINK_STATUS, &linked);
		if (!linked)
		{
			printProgramInfoLog(p);
			std::cout << "linking of shader failed...\n";
			std::cout << "*** Using fixed function pipeline ***\n";
			std::cout << "--------------------------------\n";
			return;
		}
		printProgramInfoLog(p);
		glUseProgram(p);
	}
};


class Cube
{
private:
			float rotation;

public:
	Cube()
	{
		rotation = 0;
	}

	void draw()
	{
		GLfloat n[6][3] = {  /* Normals for the 6 faces of a cube. */
			{-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0},
			{0.0, -1.0, 0.0}, {0.0, 0.0, 1.0}, {0.0, 0.0, -1.0} };
			GLint faces[6][4] = {  /* Vertex indices for the 6 faces of a cube. */
				{0, 1, 2, 3}, {3, 2, 6, 7}, {7, 6, 5, 4},
				{4, 5, 1, 0}, {5, 6, 2, 1}, {7, 4, 0, 3} };
				GLfloat v[8][3];  /* Will be filled in with X,Y,Z vertexes. */
				v[0][0] = v[1][0] = v[2][0] = v[3][0] = -1;
				v[4][0] = v[5][0] = v[6][0] = v[7][0] = 1;
				v[0][1] = v[1][1] = v[4][1] = v[5][1] = -1;
				v[2][1] = v[3][1] = v[6][1] = v[7][1] = 1;
				v[0][2] = v[3][2] = v[4][2] = v[7][2] = 1;
				v[1][2] = v[2][2] = v[5][2] = v[6][2] = -1;  


		int i;
		for (i = 0; i < 6; i++) {
			glBegin(GL_QUADS);
			glNormal3fv( n[i] );

			glVertex3fv(&v[faces[i][0]][0]);
			glVertex3fv(&v[faces[i][1]][0]);
			glVertex3fv(&v[faces[i][2]][0]);
			glVertex3fv(&v[faces[i][3]][0]);
			glEnd();
		}
	}


};


double lerp( double x1, double x2, double t)
{
	return x1 + (x2-x1)*t;
}



double rnd()
{
	return rand()/(double)RAND_MAX;
}

// http://steinsoft.net/index.php?site=Programming/Code%20Snippets/OpenGL/gluperspective
void glusPerspective( GLdouble fovy, GLdouble aspect, GLdouble near, GLdouble far )
{
	GLdouble half_height = near * tan( fovy * M_PI / 360.0 );
	GLdouble half_width = half_height * aspect;

	glFrustum( -half_width, half_width, -half_height, half_height, near, far );
}

void initGL()
{
	GLfloat light_amb[] = { 0.2f, 0.2f, 0.2f, 1.0f };
	GLfloat light_dif[] = { 0.8f, 0.8f, 0.8, 1.0f };
	GLfloat light_spc[] = { 0.5f, 0.5f, 0.5f, 1.0f };
	GLfloat light_position[] = {-1.0, 1.0, 10.0, 1.0};  /* Infinite light location. */
	

	/* Enable a single OpenGL light. */
	glLightfv(GL_LIGHT0, GL_AMBIENT, light_amb);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, light_dif);
	glLightfv(GL_LIGHT0, GL_SPECULAR, light_spc);
	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glEnable(GL_LIGHT0);
	glEnable(GL_LIGHTING);

	
	glEnable(GL_COLOR_MATERIAL);
	// set material properties which will be assigned by glColor
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
	glMateriali(GL_FRONT, GL_SHININESS, 96);

	/* Use depth buffering for hidden surface elimination. */
	glEnable(GL_DEPTH_TEST);


}

//extern bool Keys[GLFW_KEY_LAST];  // Key monitor
bool Keys[GLFW_KEY_LAST] = {false};  // Key monitor

// Handle keys - updates the Keys array
void KeyCallback(int key, int action)
{
	Keys[key] = (action == GLFW_PRESS);

}

void winResiz(int w, int h)
{
	glViewport(0,0,w,h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glusPerspective(40, double(w)/double(h), 0.01, 100);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	printf("resiz %d %d\n", w, h);	
}


int main()
{

	int resX = 640;
	int resY = 480;

	glfwInit();
	glfwOpenWindow(resX,resY,0,0,0,0,0,0,GLFW_WINDOW);

	glfwSetKeyCallback(KeyCallback);
	glfwSetWindowSizeCallback( winResiz );

	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		/* Problem: glewInit failed, something is seriously wrong. */
		fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
		exit(1);
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));

	if (GLEW_VERSION_2_0)
		printf("Ready for OpenGL 2.0\n");
	else {
		printf("OpenGL 2.0 not supported\n");
		exit(1);
	}

	ShaderManager man;
	man.setShaders();

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	//glOrtho(0,glm.resX,glm.resY, 0, -1,1);

	glusPerspective(40, resX/resY, 0.01, 100);


	glMatrixMode(GL_MODELVIEW);
	initGL();

	Cube myCube;
	TesselCube myTesselatedCube;
	myTesselatedCube.init();

	double rotation = 0;
	int drawThingy = 2;

	double tim = glfwGetTime();
	double dt = 0.01;
	while( glfwGetWindowParam(GLFW_OPENED) == GL_TRUE )
	{
		double now = glfwGetTime();
		dt = now - tim;
		tim = now;
		int mousx,mousy;
		glfwGetMousePos(&mousx,&mousy);

		int mouseLeft = glfwGetMouseButton(GLFW_MOUSE_BUTTON_1);
		int mouseRight = glfwGetMouseButton(GLFW_MOUSE_BUTTON_2);

		int keyT = glfwGetKey('T');
		if(Keys['T'])
		{
			Keys['T'] =false;
			drawThingy++;
			drawThingy = drawThingy % 3;
		}

		//int keyR = glfwGetKey('R');
		if(Keys['R'])
		{
			Keys['R'] =false;
			man.setShaders();
		}
		//float force = ( glfwGetKey('W') - glfwGetKey('S') ) * glm.dt * 128;
		//pa += ( glfwGetKey('D')-glfwGetKey('A') )*4*glm.dt;

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glLoadIdentity();
		glTranslatef(0,0,-10);
		
		glRotatef( rotation, 1, 1, 0);
		glColor3f(1,1,1);
		//FastTeapot(5, 1.1);
		rotation += 100 * dt;
		//myCube.draw();


		switch( drawThingy )
		{
			case 0: myTesselatedCube.drawSphere(); break;
			case 1: myTesselatedCube.drawCube(); break;
			case 2: myTesselatedCube.morph( -1+3*(double)mousx / resX); break;
			
		}
		glfwSwapBuffers();
		glfwSleep( 1.0 / 1000.0 );
		
	}

	glfwTerminate();
}